package cn.syy.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.syy.constants.Constants;
import cn.syy.constants.GlobalExceptionCode;
import cn.syy.exception.GlobalException;
import cn.syy.exception.GlobalExceptionHandler;
import cn.syy.mapper.PermissionMapper;
import cn.syy.pojo.domain.Login;
import cn.syy.mapper.LoginMapper;
import cn.syy.pojo.domain.Permission;
import cn.syy.pojo.dto.AdminLoginDto;
import cn.syy.pojo.dto.LoginInfoDto;
import cn.syy.pojo.dto.UpdatePasswordDto;
import cn.syy.pojo.param.RegisterSaveLoginParam;
import cn.syy.pojo.vo.LoginInfoVo;
import cn.syy.propertites.SaTokenSettingProperties;
import cn.syy.service.ILoginService;
import cn.syy.template.AppWeChatTemplate;
import cn.syy.utils.AssertUtil;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.sf.jsqlparser.statement.select.KSQLJoinWindow;
import net.sf.jsqlparser.statement.select.KSQLWindow;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.TimeoutUtils;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 登录表 服务实现类
 * </p>
 *
 * @author sysyy
 * @since 2024-07-31
 */
@Service
public class LoginServiceImpl extends ServiceImpl<LoginMapper, Login> implements ILoginService {
    @Autowired
    private AppWeChatTemplate appWeChatTemplate;

    @Autowired
    private PermissionMapper permissionMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private SaTokenSettingProperties saTokenSettingProperties;
    @Autowired
    private LoginMapper loginMapper;


    /**
     * 注册保存
     * @param registerSaveLoginParam
     */
    @Override
    public void registerSaveLogin(RegisterSaveLoginParam registerSaveLoginParam) {
        Login login = new Login();
        // ignoreCase：是否忽略大小写
        BeanUtil.copyProperties(registerSaveLoginParam,login,false); // 将registerSaveLoginParam对象的值复制给login对象
        login.setCreateTime(new Date()).setVersion(0).setDeleted(false);
        super.save(login);
    }

    /**
     * 登录
     * @param code
     * @return
     */
    @Override
    public Object wxlogin(String code,Integer type) {
        // 1. 参数校验 JSR303（已经在controller层里实现）
        // 2. 业务校验
        // 2.1 获取openid
        String openId = appWeChatTemplate.getOpenIdByCode(code);
        // 2.2 根据openid获取login对象并判断是否登陆过
        Login login = super.getOne(new LambdaQueryWrapper<Login>().eq(Login::getOpenId, openId).eq(Login::getType,type));
        AssertUtil.isNotNull(login,GlobalExceptionCode.PARAM_CODE_ERROR);
        // 3. 业务实现
        // 3.1 获取loginid
        Long loginId = login.getId();
        // 3.2 使用sa-Token进行登录
        StpUtil.login(loginId);
        // 3.3 使用sa-Token获取token
        String tokenName = StpUtil.getTokenName();
        String tokenValue = StpUtil.getTokenValue();
        // 3.4 通过login获取权限信息
        List<String> permissions = permissionMapper.selectByLoginId(loginId);
        // 3.5 将登录人的权限信息保存到redis中，key就是登录人的loginId
        String key = String.format(saTokenSettingProperties.getPermissionKey(), loginId);
        redisTemplate.opsForValue().set(key,permissions,StpUtil.getTokenTimeout(),TimeUnit.SECONDS);
        // 3.6 返回前端的内容包括token信息，权限信息，登录人信息
        LoginInfoDto loginInfoDto = BeanUtil.copyProperties(login, LoginInfoDto.class);
        return new LoginInfoVo(tokenName,tokenValue,permissions,loginInfoDto);

    }

    /**
     * 后台登录
     * @param adminLoginDto
     * @return
     */
    @Override
    public LoginInfoVo adminLogin(AdminLoginDto adminLoginDto) {
        // 1.参数校验：JSR303
        // 2.业务校验
        // 2.1 账号和type校验,超找是否存在
        Login login = super.getOne(new LambdaQueryWrapper<Login>()
                .eq(Login::getUsername, adminLoginDto.getUsername())
                .eq(Login::getType, adminLoginDto.getType()));
        // 2.2密码校验
        boolean checkpw = BCrypt.checkpw(adminLoginDto.getPassword(), login.getPassword());
        if (!checkpw){
            throw new GlobalException(GlobalExceptionCode.PASSWORD_ERROR);
        }
        // 3.业务实现
        // 3.0 使用sa-token进行登录
        Long loginId = login.getId();
        StpUtil.login(loginId);
        // 3.1 获取tokenName
        String tokenName = StpUtil.getTokenName();
        // 3.2 获取tokenValue
        String tokenValue = StpUtil.getTokenValue();
        // 3.3 获取权限并且加到redis中
        List<String> permissions = permissionMapper.selectByLoginId(loginId);
        String key = String.format(saTokenSettingProperties.getPermissionKey(), loginId);
        redisTemplate.opsForValue().set(key,permissions,StpUtil.getTokenTimeout(), TimeUnit.SECONDS);
        // 3.3 复制login信息到logininfoDto里
        LoginInfoDto loginInfoDto = BeanUtil.copyProperties(login, LoginInfoDto.class);
        // 3.4 返回一个logininfoVo对象
        return new LoginInfoVo(tokenName,tokenValue,permissions,loginInfoDto);
    }

    /**
     * 后台退出
     */
    @Override
    public void logout() {
        //  获取当前会话账号id, 并转化为`long`类型
        String key = String.format(saTokenSettingProperties.getPermissionKey(), StpUtil.getLoginIdAsLong());
        // 删除redis里面的后台登录缓存
        redisTemplate.delete(key);
        // satoken退出登录，删除token
        StpUtil.logout();
    }

    /**
     * 修改密码
     * @param updatePassword
     */
    @Override
    public void updatePassword(UpdatePasswordDto updatePassword) {
        // 1. 参数校验

        // 2. 业务校验
        // 2.1 判断登录人的旧密码是否正确
        long loginId = StpUtil.getLoginIdAsLong();
        Login login = super.getOne(new LambdaQueryWrapper<Login>(getById(loginId)));
        boolean checkpw = BCrypt.checkpw(updatePassword.getOldPassword(), login.getPassword());
        if (!checkpw){
            throw new GlobalException(GlobalExceptionCode.PASSWORD_ERROR);
        }
        // 1.1 由于前端对于参数的格式进行校验过，所以需要判断两次的密码是否一致
        if (updatePassword.getNewPassword().equals(updatePassword.getOldPassword())){
            throw new GlobalException(GlobalExceptionCode.PASSWORD_SAME_ERROR);
        }
        // 3. 业务实现
        // 3.1 修改密码为新的密码然后存储到login表里
        String encryptedPassword = BCrypt.hashpw(updatePassword.getNewPassword(), BCrypt.gensalt());
        login.setPassword(encryptedPassword);
        super.saveOrUpdate(login);
    }
}
